﻿/*
	Copyright 2010 Intuitive Solutions

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

using System;
using System.IO;
using System.Reflection;
using Dynamo.Core.Attributes;
using System.Collections.Generic;
using Dynamo.Core.DesignPatterns.Singleton;

namespace Dynamo.Core.Plugins
{
	public class PluginManager
	{
		private static PluginManager pmInstance;

		private Dictionary<string, Plugin> dPlugins;

		private PluginManager()
		{
			dPlugins = new Dictionary<string, Plugin>();
		}

		public static PluginManager Instance
		{
			get
			{
				if (pmInstance == null)
				{
					pmInstance = new PluginManager();
				}

				return pmInstance;
			}
		}

		public void AddPlugin(string sPluginName, Assembly aPluginAssembly)
		{
			Plugin pPlugin = new Plugin();

			pPlugin.PluginName	= sPluginName;
			pPlugin.Assembly	= aPluginAssembly;

			dPlugins.Add(sPluginName, pPlugin);
		}

		/*public void LoadAllPlugins()
		{
			lock (typeof(PluginManager))
			{
				AppDomain adTemporalDomain = AppDomain.CreateDomain("DynamoTemporalDomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);

				Assembly aTemporal = LoadAssembly(adTemporalDomain, Assembly.GetEntryAssembly(), sPluginName);

				if (aTemporal != null)
				{
					AssemblyName anAssemblyName = aTemporal.GetName();

					Plugin pPlugin = new Plugin();
					pPlugin.PluginName = sPluginName;
					pPlugin.Assembly = Assembly.Load(anAssemblyName);

					dPlugins.Add(sPluginName, pPlugin);
				}
				else
				{
					string sExecutingLocation = GetDirectory();

					foreach (string sItem in System.IO.Directory.GetFiles(sExecutingLocation, "*.dll"))
					{
						string sItem2 = sItem.Replace(sExecutingLocation, "");

						sItem2 = sItem2.Remove(sItem2.Length - ".dll".Length);

						if (!IsIgnoredAssembly(sItem2))
						{
							Assembly a = adTemporalDomain.Load(AssemblyName.GetAssemblyName(sItem));

							if (IsPlugin(a, sPluginName))
							{
								aTemporal = a;

								AssemblyName anAssemblyName = aTemporal.GetName();

								Plugin pPlugin = new Plugin();
								pPlugin.PluginName = sPluginName;
								pPlugin.Assembly = Assembly.Load(anAssemblyName);

								dPlugins.Add(sPluginName, pPlugin);

								break;
							}
						}
					}
				}

				AppDomain.Unload(adTemporalDomain);
			}
		}*/

		public void LoadPlugin(string sPluginName)
		{
			LoadPlugin(sPluginName, false);
		}

		public void LoadPlugin(string sPluginName, bool bIsUnloadable)
		{
			lock (typeof(PluginManager))
			{
				AppDomain adTemporalDomain = AppDomain.CreateDomain("DynamoTemporalDomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);

				Assembly aTemporal = LoadAssembly(adTemporalDomain, Assembly.GetEntryAssembly(), sPluginName);

				if (aTemporal != null)
				{
					AssemblyName anAssemblyName = aTemporal.GetName();

					Plugin pPlugin = new Plugin();
					pPlugin.PluginName = sPluginName;
					pPlugin.Assembly = Assembly.Load(anAssemblyName);

					dPlugins.Add(sPluginName, pPlugin);
				}
				else
				{
					string sExecutingLocation = GetDirectory();

					foreach (string sItem in System.IO.Directory.GetFiles(sExecutingLocation, "*.dll"))
					{
						string sItem2 = sItem.Replace(sExecutingLocation, "");

						sItem2 = sItem2.Remove(sItem2.Length - ".dll".Length);

						if (!IsIgnoredAssembly(sItem2))
						{
							Assembly a = adTemporalDomain.Load(AssemblyName.GetAssemblyName(sItem));

							if (IsPlugin(a, sPluginName))
							{
								aTemporal = a;

								AssemblyName anAssemblyName = aTemporal.GetName();

								Plugin pPlugin = new Plugin();
								pPlugin.PluginName = sPluginName;
								pPlugin.Assembly = Assembly.Load(anAssemblyName);

								dPlugins.Add(sPluginName, pPlugin);

								break;
							}
						}
					}
				}

				AppDomain.Unload(adTemporalDomain);
			}
		}

		public string GetDirectory()
		{
			string sExecutingLocation = AppDomain.CurrentDomain.RelativeSearchPath == null ? AppDomain.CurrentDomain.BaseDirectory : AppDomain.CurrentDomain.RelativeSearchPath;

			//Mono web service!!!
			if (sExecutingLocation.IndexOf(";") >= 0)
			{
				string[] saDirectories = sExecutingLocation.Split(';');

				foreach (string sDirectory in saDirectories)
				{
					if (System.IO.Directory.Exists(sDirectory))
					{
						sExecutingLocation = sDirectory;

						break;
					}
				}
			}
			return sExecutingLocation;
		}

		public Assembly GetAssembly(string sPluginName)
		{
			Assembly aAssembly = null;

			if (dPlugins.ContainsKey(sPluginName))
			{
				aAssembly = dPlugins[sPluginName].Assembly;
			}
			else
			{
				aAssembly = LoadAssembly(sPluginName);

				Plugin pPlugin = new Plugin();
				pPlugin.Assembly = aAssembly;

				dPlugins.Add(sPluginName, pPlugin);
			}

			return aAssembly;
		}

		public Assembly LoadAssembly(string sPluginName)
		{
			Assembly aLoadedAssembly = LoadAssembly(AppDomain.CurrentDomain, Assembly.GetEntryAssembly(), sPluginName);

			if (aLoadedAssembly == null)
			{
				string sLocation = GetDirectory();

				sLocation += System.IO.Path.DirectorySeparatorChar + sPluginName + ".dll";

				aLoadedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(sLocation));
			}

			return aLoadedAssembly;
		}

		public Assembly LoadAssembly(AppDomain adApplicationDomain, Assembly aRootAssembly, string sAssemblyName)
		{
			Assembly aLoadedAssembly = null;
			bool bAssemblyFound = false;

			if (aRootAssembly != null)
			{
				if (aRootAssembly.GetName().Name.Equals(sAssemblyName))
				{
					aLoadedAssembly = aRootAssembly;

					bAssemblyFound = true;
				}
				foreach (AssemblyName anReferencedAssembly in aRootAssembly.GetReferencedAssemblies())
				{
					if (!IsIgnoredAssembly(anReferencedAssembly.Name))
					{
						if (anReferencedAssembly.Name.Equals(sAssemblyName))
						{
							aLoadedAssembly = adApplicationDomain.Load(anReferencedAssembly);

							bAssemblyFound = true;

							break;
						}
						else
						{
							Assembly aTmp = adApplicationDomain.Load(anReferencedAssembly);

							if (IsPlugin(aTmp, sAssemblyName))
							{
								aLoadedAssembly = aTmp;

								bAssemblyFound = true;

								break;
							}
						}
					}
				}

				if (!bAssemblyFound)
				{
					foreach (AssemblyName anReferencedAssembly in aRootAssembly.GetReferencedAssemblies())
					{
						if (!IsIgnoredAssembly(anReferencedAssembly.Name))
						{
							aLoadedAssembly = LoadAssembly(adApplicationDomain, adApplicationDomain.Load(anReferencedAssembly), sAssemblyName);

							if (aLoadedAssembly != null)
							{
								break;
							}
						}
					}
				}
			}
			else
			{
				foreach (Assembly aDomainAssembly in AppDomain.CurrentDomain.GetAssemblies())
				{
					//if (!IsIgnoredAssembly(aDomainAssembly.GetName().Name))
					if (aDomainAssembly.GetName().Name.ToLower().StartsWith("app_code."))
					{
						aLoadedAssembly = LoadAssembly(adApplicationDomain, aDomainAssembly, sAssemblyName);

						if (aLoadedAssembly != null)
						{
							break;
						}
					}
				}
			}

			return aLoadedAssembly;
		}

		private bool IsIgnoredAssembly(string sAssemblyName)
		{
			if (sAssemblyName.ToLower().StartsWith("system") ||
				sAssemblyName.ToLower().StartsWith("microsoft") ||
				sAssemblyName.ToLower().StartsWith("mySql") ||
				sAssemblyName.ToLower().StartsWith("log4net") ||
				sAssemblyName.ToLower().StartsWith("mscorlib") ||
				sAssemblyName.ToLower().StartsWith("6to4svc") ||
				sAssemblyName.ToLower().StartsWith("dynamo.core") ||
				sAssemblyName.ToLower().StartsWith("webdev") ||
				sAssemblyName.ToLower().StartsWith("antlr3") ||
				sAssemblyName.ToLower().StartsWith("uiautomationtypes") ||
				sAssemblyName.ToLower().StartsWith("presentationframework") ||
				sAssemblyName.ToLower().StartsWith("windowsbase") ||
				sAssemblyName.ToLower().StartsWith("presentationcore"))
			{
				return true;
			}

			return false;
		}

		private bool IsPlugin(Assembly aTmp, string sAssemblyName)
		{
			PluginConfigurationAttribute[] tst = (PluginConfigurationAttribute[])aTmp.GetCustomAttributes(typeof(PluginConfigurationAttribute), false);

			for (int i = 0; i < tst.Length; i++)
			{
				if (tst[i].Name.Equals(sAssemblyName))
				{
					return true;
				}
			}

			return false;
		}

		public Type[] GetProviderTypes<ProviderType>(string sPluginName)
		{
			Type tProviderType = typeof(ProviderType);
			List<Type> lImplementations = new List<Type>();

			if (!dPlugins.ContainsKey(sPluginName))
			{
				LoadPlugin(sPluginName);
			}

			foreach (Type tClass in dPlugins[sPluginName].Assembly.GetTypes())
			{
				if (!tClass.IsAbstract &&
					!tClass.IsInterface &&
					tProviderType.IsAssignableFrom(tClass))
				{
					foreach (Type tInterfaceType in tClass.GetInterfaces())
					{
						if (!tProviderType.Equals(tInterfaceType) &&
							tProviderType.IsAssignableFrom(tInterfaceType))
						{
							lImplementations.Add(tClass);
							break;
						}
					}
				}
			}

			return lImplementations.ToArray();
		}

		public ProviderType Get<ProviderType>(string sProviderName)
		{
			ProviderType ptProviderInstance = default(ProviderType);

			if (!dPlugins.ContainsKey(sProviderName))
			{
				LoadPlugin(sProviderName);
			}

			Type tImplementacionType = GetProviderType(dPlugins[sProviderName], typeof(ProviderType));

			ptProviderInstance = GetInstance<ProviderType>(sProviderName, tImplementacionType);

			return ptProviderInstance;
		}

		public object GetInstance(String sClassDynamoQualifiedName)
		{
			object oInstance = null;

			foreach (string sPluginName in dPlugins.Keys)
			{
				 if(sClassDynamoQualifiedName.StartsWith(sPluginName))
				 {
					foreach (Type tClass in dPlugins[sPluginName].Assembly.GetTypes())
					{
						if (!tClass.IsAbstract &&
							!tClass.IsInterface &&
							tClass.Name.Equals(sClassDynamoQualifiedName.Replace(sPluginName + ".", "")))
						{
							oInstance = CreateInstance(dPlugins[sPluginName], tClass);
						}
					}
				 }
			}

			return oInstance;
		}

		public object GetObjectInstance(string sProviderName, Type tImplementacionType, Type tProviderType)
		{
			return this.GetType().GetMethod("GetInstance", new Type[] { typeof(string), typeof(Type) }).MakeGenericMethod(tProviderType).Invoke(this, new object[] { sProviderName, tImplementacionType });
		}

		public ProviderType GetInstance<ProviderType>(string sProviderName, Type tImplementacionType)
		{
			ProviderType ptProviderInstance = default(ProviderType);

			if (SingletonManager.Instance.HasInstance(sProviderName, tImplementacionType))
			{
				ptProviderInstance = SingletonManager.Instance.Get<ProviderType>(sProviderName, tImplementacionType);
			}
			else
			{
				ptProviderInstance = (ProviderType)CreateInstance(dPlugins[sProviderName], tImplementacionType);

				InjectDependencies(dPlugins[sProviderName], ptProviderInstance, typeof(ProviderType));
			}
			return ptProviderInstance;
		}

		public Type GetProviderType(Plugin pPlugin, Type tProviderType)
		{
			foreach (Type tType in pPlugin.Assembly.GetTypes())
			{
				if (tProviderType.IsAssignableFrom(tType))
				{
					return tType;
				}
			}

			return null;
		}

		public object CreateInstance(Plugin pPlugin, Type tType)
		{
			object ptInstance = null;

			if (SingletonManager.Instance.IsSingleton(tType))
			{
				if (SingletonManager.Instance.HasInstance(pPlugin.PluginName, tType))
				{
					ptInstance = SingletonManager.Instance.Get<object>(pPlugin.PluginName, tType);
				}
				else
				{
					ptInstance = tType.Assembly.CreateInstance(tType.ToString());

					SingletonManager.Instance.Add(pPlugin.PluginName, ptInstance);
				}
			}
			else
			{
				ptInstance = tType.Assembly.CreateInstance(tType.ToString());
			}

			return ptInstance;
		}

		public void InjectDependencies(Plugin pPlugin, object ptProviderInstance, Type tProviderType)
		{
			foreach (PropertyInfo piPropertyInfo in tProviderType.GetProperties())
			{
				if (piPropertyInfo.IsDefined(typeof(InjectAttribute), true))
				{
					Type tFoundType = null, tFoundInterface = null;

					if (piPropertyInfo.PropertyType.FullName.StartsWith("Dynamo.Core.Plugins.IFactory"))
					{
						DynamicBuilder db = new DynamicBuilder();
						Dictionary<string, object> d = new Dictionary<string,object>();
						
						string sTemplate = new System.IO.StreamReader(pmInstance.GetType().Assembly.GetManifestResourceStream("Dynamo.Core.Resources.Factory.st")).ReadToEnd();

						d.Add("class", "Factory");
						d.Add("model", GetFactoryModel(pPlugin, piPropertyInfo.PropertyType.GetGenericArguments()[0]));

						object o = db.Build(new Assembly[] { pPlugin.Assembly, tProviderType.Assembly, this.GetType().Assembly }, sTemplate, d);

						if (o != null)
						{
							piPropertyInfo.SetValue(ptProviderInstance, o, null);
						}
					}
					else
					{
						if (piPropertyInfo.PropertyType.IsDefined(typeof(ProviderAttribute), true))
						{
							tFoundType = FindInterfaceImplementation(pPlugin.Assembly, piPropertyInfo.PropertyType);
						}

						if (tFoundType == null)
						{
							tFoundType = FindInterfaceImplementation(tProviderType.Assembly, piPropertyInfo.PropertyType);
						}

						tFoundInterface = piPropertyInfo.PropertyType;

						if (tFoundType != null)
						{
							piPropertyInfo.SetValue(ptProviderInstance, GetObjectInstance(pPlugin.PluginName, tFoundType, tFoundInterface) /*CreateInstance(pPlugin, tFoundType)*/, null);
						}
					}
				}
			}
		}

		public Type[] GetImplementations(Plugin pPlugin, Type tProviderType)
		{
			List<Type> lImplementations = new List<Type>();

			foreach (Type tClass in pPlugin.Assembly.GetTypes())
			{
				if (!tClass.IsAbstract &&
					!tClass.IsInterface &&
					tProviderType.IsAssignableFrom(tClass))
				{
					foreach (Type tInterfaceType in tClass.GetInterfaces())
					{
						if (!tProviderType.Equals(tInterfaceType) &&
							tProviderType.IsAssignableFrom(tInterfaceType))
						{
							lImplementations.Add(tClass);
							break;
						}
					}
				}
			}

			return lImplementations.ToArray();
		}

		public FactoryModel GetFactoryModel(Plugin pPlugin, Type tProviderType)
		{
			FactoryModel fmFactoryModel = new FactoryModel();

			fmFactoryModel.ReturnType = tProviderType.FullName;

			Dictionary<Type, Type> dIgnoredTypes = new Dictionary<Type, Type>();

			dIgnoredTypes.Add(tProviderType, tProviderType);

			if (tProviderType.IsDefined(typeof(ProviderAttribute), true))
			{
				Dictionary<Type, Type> dFoundTypes = new Dictionary<Type, Type>();

				foreach (Type tClass in pPlugin.Assembly.GetTypes())
				{
					if (!tClass.IsAbstract &&
						!tClass.IsInterface &&
						tProviderType.IsAssignableFrom(tClass))
					{
						foreach (Type tInterfaceType in tClass.GetInterfaces())
						{
							if (!dFoundTypes.ContainsKey(tInterfaceType))
							{
								dFoundTypes.Add(tInterfaceType, tInterfaceType);
							}
							else
							{
								if (!dIgnoredTypes.ContainsKey(tInterfaceType))
								{
									dIgnoredTypes.Add(tInterfaceType, tInterfaceType);
								}
							}
						}
					}
				}
			}

			foreach (Type tClass in pPlugin.Assembly.GetTypes())
			{
				if (!tClass.IsAbstract &&
					!tClass.IsInterface &&
					tProviderType.IsAssignableFrom(tClass))
				{
					foreach (Type tInterfaceType in tClass.GetInterfaces())
					{
						if (!dIgnoredTypes.ContainsKey(tInterfaceType) &&
							tProviderType.IsAssignableFrom(tInterfaceType))
						{
							ClassIdModel cimClassIdModel = new ClassIdModel();

							cimClassIdModel.ClassImplementation	= tClass.FullName;
							cimClassIdModel.ClassInterface		= tInterfaceType.FullName;

							fmFactoryModel.Types.Add(cimClassIdModel);
						}
					}
				}
			}

			return fmFactoryModel;
		}

		public Type FindInterfaceImplementation(Assembly aAssembly, Type tInterface)
		{
			Type tFoundType = null;

			foreach (Type tType in aAssembly.GetTypes())
			{
				if (!tType.IsAbstract &&
					!tType.IsInterface)
				{
					foreach (Type tInterfaceType in tType.GetInterfaces())
					{
						if (tInterface.Equals(tInterfaceType))
						{
							tFoundType = tType;

							break;
						}
					}
				}

				if (tFoundType != null)
				{
					break;
				}
			}

			return tFoundType;
		}
	}
}
